package com.googlecode.cswish.security.auth;

import java.security.Principal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;


import org.apache.log4j.Logger;

import com.googlecode.cswish.security.type.Path;
import com.googlecode.cswish.security.type.RuleSetOr;
import com.googlecode.cswish.util.FrameConstant;


/**
 * @author Jerry.Feng Date: 2004-12-29 20:53:42
 * @version $id: $
 */
public class WebPrincipal implements Principal {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(WebPrincipal.class);

	private String userId; // user id

	/**
	 * <String, RuleSetOr> ( path,  condition)
	 */
	private Map pathCache;
	
	private Map dataLevel;

	public WebPrincipal(String userId) {
		this.userId = userId;
	}

	public WebPrincipal(String userId, Path[] paths, Map dataLevel) {
		// sort the list, alias path should be front, i.e, alias path should
		// smaller
		// than sub path.
		// Arrays.sort swaps the object only when the compare result > 0
		Arrays.sort(paths, new Comparator() {
			public int compare(Object o1, Object o2) {
				Path path1 = (Path) o1;
				Path path2 = (Path) o2;

				if (path1.compare(path2) == 1) {
					// path 1 is sub path, sub path should be back, so it should
					// be swaped.
					return 1;
				}
				return 0;
			}
		});

		this.userId = userId;
		pathCache = new HashMap((paths.length * 2) * 4 / 3);

		// for each path
		for (int i = 0; i < paths.length; i++) {
			Path path = paths[i];
			addPermission(path);
		}
		
		this.dataLevel = dataLevel;
	}

	// add current operation and the operaitons in their alias paths
	private void addPermission(Path path) {
		int index = -1;
		String sPath = path.getPath();
		RuleSetOr condition = (RuleSetOr) pathCache.get(sPath);
		if (condition == null) {
			// It is the first time that the path is added to the pathCache
			condition = path.getCondition();
			pathCache.put(sPath, condition);
		} else {
			condition = condition.addRule(path.getCondition());
			pathCache.put(sPath, condition);
		}
		
		int lastIndex = sPath.length() - 1;
		// 1. get alias operations and add them to current path
		while ((index = sPath.indexOf('/', index + 1)) >= 0 && index < lastIndex) {
			String parentPath = sPath.substring(0, index + 1);
			RuleSetOr parentCond = (RuleSetOr) pathCache
					.get(parentPath);
			if (parentCond != null) {
				condition = condition.addRule(parentCond);
				pathCache.put(sPath, condition);
			}
		}
	}
	
	public boolean permitURL(String url) {
		Parameter parameter = new Parameter(url);
		return permit(parameter);
	}
	
	// judge whether user can operate the path
	public boolean permit(String path) {		
		return getCondition(path) == null ? false : true;
	}

	// judge whether user can use "method" to operate the path
	public boolean permit(String path, String method) {
		Map parameters = new HashMap(1);
		parameters.put(FrameConstant.FORM_ACTION, method);
		Parameter parameter = new Parameter(path , parameters);
		
		return permit(parameter);
	}
	
	public boolean permit(Parameter parameter) {
		RuleSetOr condition = getCondition(parameter.getPath());
		if (condition != null) {
			// TODO:
			return Boolean.valueOf(condition.execute(null));
		}

		return false;
	}

	// 1. get condition from pathCache
	public RuleSetOr getCondition(String path) {
		RuleSetOr condition = (RuleSetOr) pathCache.get(path);

		// if can't find the operation , try to find it in the alias path
		if (condition == null) {
			char[] pathChars = path.toCharArray();
			for (int index = pathChars.length - 1; index >= 0; index--) {
				if (pathChars[index] == '/') {
					String parentPath = path.substring(0, index + 1);
					condition = (RuleSetOr) pathCache.get(parentPath);
					if (condition != null) {
						pathCache.put(path, condition);
						break;
					}
				}
			}
		}

		return condition;
	}
	
	public int getDataAccessLevel(String path) {
		Integer level = (Integer) dataLevel.get(path);
		return level == null ? 0 : level.intValue();
	}
	
	/*
	 * @see java.security.Principal#getName()
	 */
	public String getName() {
		return userId;
	}
}
